环境

32位MIPS平台,采用O32的ABI,linux操作系统,GCC编译器

需求

我们准备好C++对象的this指针,对象成员函数指针,对象的参数,然后模拟编译器来填充好参数并调用该对象成员函数. 需要注意,对象不固定,成员函数不固定,成员函数的参数个数以及类型都不固定.

MIPS的一些约定

MIPS寄存器的约定

Reg_MIPS

MIPS的O32标准ABI函数调用的约定

  • 输入参数要依次填充寄存器a0, a1, a2, a3,如果还有参数则需要填充到栈上
  • 调用函数在调用被调用函数时候,需要为被调函数预留16字节的栈上空间以备被调函数存储a0,a1,a2和a3的值,所以a0,a1,a2,a3寄存器放不下的参数需要放在被调函数的栈帧+16字节以上的空间
  • 输入参数的填充时候要遵守对齐原则,比如参数依次为int, long long那么int放在a0,long long放在a2,a3寄存器而不是a1, a2寄存器

实现

我们这里的实现机制是,把所有参数按照4字节分割,比如long long需要分割成两个4字节,然后我们把这个一一个分割后的4字节的数据按照MIPS O32的ABI的约定填充到指定寄存器和栈上,然后再调用该函数即可.

处理对齐

我们需要分析每种参数类型到底是4字节对齐还是8字节对齐等等,然后根据不同的对齐确定参数寄存器和栈的对齐

代码实现

我们以一个VARIANT结构体为例,对于其他类型可以做同样的处理

#define DWORD 4
#define __ASM_CFI(str) str
#define __ASM_DEFINE_FUNC(name,suffix,code) asm(".text\n\t.align 4\n\t.globl " #name suffix "\n\t.type " #name suffix ",@function\n" #name suffix ":\n\t.cfi_startproc\n\t" code "\n\t.cfi_endproc\n\t.previous");
#define __ASM_GLOBAL_FUNC(name,code) __ASM_DEFINE_FUNC(name,"",code)

typedef intptr_t (CALLBACK *FARPROC)();

extern "C" uintptr_t CDECL call_method(void *func, int nb_args, const unsigned int *args, int nb_fargs, const double* fargs);
__ASM_GLOBAL_FUNC( call_method,
					".set noreorder\r\n"
					".set arch=mips3\r\n"
					"sw $16,-28($29)\r\n" // $16压栈
					"sw $17,-32($29)\r\n" // $16压栈
					"move $16, $29\r\n" // store $29
					"sll $17, $5, 2\r\n"
					"and $17, $17,0xffffff80\r\n"
					"addiu $17, $17, 256\r\n"  // stack size
					"subu $29,$29,$17\r\n"
					"sw $31,-4($16)\r\n"   //将返回地址压入堆栈
					"sw $30,-8($16)\r\n"   //将fp压入堆栈
					"sdc1 $f12,-16($16)\r\n"  //将f12到f15寄存器保存到堆栈中
					"sdc1 $f14,-24($16)\r\n"
					"move $30,$29\r\n"       //将$29保存到fp
					"sw $4,0($16)\r\n"       //将参数*func压栈
					"sw $5,4($16)\r\n"       //将参数nb_args压栈
					"sw $6,8($16)\r\n"       //将参数*args压栈
					"sw $7,12($16)\r\n"      //将参数nb_fargs压栈
					"move $11,$7\r\n"        //将nb_fargs存入$11
					"lw $10,16($29)\r\n"     //将*fargs存入$10
					"addu $11,$11,-2\r\n"     //nb_fargs-2
					"bltz $11, 1f\r\n"       //如果nb_fargs-2<0,跳转到1
					"nop \r\n"
					"ldc1 $f14,8($10)\r\n"   //将*fargs[1]移入$f14(高位隐含存入$f15)
					"1: addu $11,$11,1\r\n"
					"bltz $11,2f\r\n"        //如果nb_fargs-1<0,跳转到2
					"nop \r\n"
					"ldc1 $f12,0($10)\r\n"   //将*fargs[2]移入$f12,$f13
					"2: move $8,$5\r\n"      //将nb_args 移入$8
					"move $9,$6\r\n"         //将*args移入$9
					"blez $8, 4f\r\n"        //如$8小于等于0调转到call func
					"nop\r\n"
					"lw $4,($9)\r\n"         //将args[0]移入$4
					"addu $8,$8,-1\r\n"      //$8=$8-1
					"blez $8,4f\r\n"         //如$8小于等于0跳转到call func
					"nop\r\n"
					"lw $5,4($9)\r\n"        //将args[1]移入$5
					"addu $8,$8,-1\r\n"      //$8=$8-1
					"blez $8, 4f\r\n"        //如$8小于等于0跳转到call func
					"nop\r\n"
					"lw $6,8($9)\r\n"        //将args[2]移入$6
					"addu $8,$8,-1\r\n"      //$8=$8-1
					"blez $8,4f\r\n"         //如$8小于等于0跳转到call func
					"nop \r\n"
					"lw $7,12($9)\r\n"       //将args[3]移入$7
					"addu $9,$9,16\r\n"      //将指针移到args[4]
					"addu $11,$29,16\r\n"     //$11=$29+16
					"3: addu $8,$8,-1\r\n"   //$8=$8-1
					"blez $8,4f\r\n"         //如$8小于等于0跳转到call func
					"nop\r\n"
					"lw $10,($9)\r\n"        //将args[n]移入$10
					"sw $10,($11)\r\n"        //将$10存入$29+16+m
					"addu $9,$9,4\r\n"       //指向args[n+1]
					"addu $11,$11,4\r\n"      //指向$29+16+m
					"b 3b\r\n"  	         //跳转到
					"nop\r\n"
					"4: lw $25,0($16)\r\n"   //将*func存入$25
					"nop\r\n"
					"jalr $25\r\n"             //call func
					"nop\r\n"
					"ldc1 $f14,-24($16)\r\n"
					"ldc1 $f12,-16($16)\r\n"
					"lw $30,-8($16)\r\n"
					"lw $31,-4($16)\r\n"
					"lw $16, -28($16)\r\n"
					"addu  $29,$29,$17\r\n"
					"lw $17, -32($29)\r\n"
					"jr $31\r\n"
					"nop\r\n"
					".set reorder\r\n")

struct VARIANT
{
	WORD a; //2 字节
	WORD b;
	WORD c;
	WORD d;
	INT64 e; // 8 字节

};

class A{
public:
	int fun1(VARIANT a, int c, int d){
		c = c + d;
		return 12345;
	}
};

int main(int argc,char *argv[])
{
		VARIANT var = {0,1,2,3,4};
		A a;
		std::vector<uint32> args(100);
		int argspos = 0;

		//this指针作为第一个参数
		args[argspos++] = (unsigned int)&a;
		//准备第二个参数,注意对齐
		if(argspos % 2 == 1)	argspos++;
		memcpy( &args[argspos], &var, sizeof(var) );
		argspos += sizeof(var) / sizeof(DWORD);
		//准备第三和第四个参数
		args[argspos++] = 3333;
		args[argspos++] = 4444;
		
		//调用汇编编写的模拟调用
		int ret = call_method2((void*)(&A::fun1), argspos, &args[0], 0, NULL);
}

这个只是简单的以VARIANT举例,为了更加通用的处理各种参数类型,需要对参数类型进行分类讨论然后采用与此类似的操作.

其实,这段实现也可以完全C语言化,那么我们就需要根据参数个数写一个大switch…case针对每种参数个数显示的调用func:

switch(num_args){
	case 1:	func(this, args[0]);
	case 2:	func(this, args[0], args[1]);
	case 3:	func(this, args[0], args[1], args[2]);
	...
}

这样做很简单清楚,不过就难度太小了…

- EOF -

声明:本文采用BY-NC-SA协议进行授权.转载请注明: Mips下仿照编译器实现C++成员函数调用



comments powered by Disqus

Hitwebcounter.com Free